package org.luxor.cloud.gateway.component.trace;

import com.fasterxml.jackson.databind.JsonNode;
import io.micrometer.core.instrument.Tags;
import org.apache.commons.lang.StringUtils;
import org.luxor.cloud.gateway.component.Jackson2Mapper;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

/**
 * 指标采集器
 *
 * @author Mr.Yan  @date: 2020/11/10
 */
public class Collector {
    private static final String UNKNOWN = "unknown";
    private final List<MediaType> readable_media_types = Arrays.asList(MediaType.APPLICATION_JSON_UTF8
            , MediaType.APPLICATION_JSON);
    /**
     * 开始时间
     */
    private Long startTimeMillis;
    /**
     * 请求内容
     */
    private String requestBody;
    /**
     * 响应内容
     */
    private String responseBody;

    /**
     * 日志存储注册器
     */
    private TraceRegistry registry;

    private Collector(TraceRegistry registry) {
        Assert.notNull(registry, "TraceRegistry not is null!");
        this.registry = registry;
        this.startTimeMillis = System.currentTimeMillis();
        this.requestBody = StringUtils.EMPTY;
        this.responseBody = StringUtils.EMPTY;
    }

    /**
     * 开始
     */
    public static Collector start(TraceRegistry registry) {
        return new Collector(registry);
    }

    /**
     * 设置请求内容
     */
    public void setRequestBody(String requestBody) {
        this.requestBody = requestBody;
    }

    /**
     * 设置响应内容
     */
    public void setResponseBody(String responseBody) {
        this.responseBody = responseBody;
    }

    /**
     * 结束
     */
    public void stop(ServerWebExchange exchange) {
        // 计算处理时长(毫秒)
        String elapsedTimeMillis = String.valueOf(System.currentTimeMillis() - startTimeMillis);

        // 汇总指标数据项
        Tags tags = Tags.of("RequestURL", exchange.getRequest().getURI().toString()
                , "RequestMethod", exchange.getRequest().getMethodValue()
                , "RequestContent", getRequestContent(exchange.getRequest())
                , "Device", getDevice(exchange.getRequest())
                , "RemoteAddress", getRemoteAddr(exchange.getRequest())
                , "ClientId", getClientId(exchange)
                , "UserName", getUsername(exchange)
                , "RouteUri", getRouteUri(exchange)
                , "RouteId", getRouteId(exchange)
                , "ResponseContent", getResponseContent(exchange.getResponse())
                , "ResponseCode", getResponseCode(exchange)
                , "ResponseMessage", getResponseMessage(exchange)
                , "Status", String.valueOf(getHttpStatus(exchange.getResponse()).value())
                , "ElapsedTimeMillis", elapsedTimeMillis);

        // 记录采集结果
        registry.record(tags);
    }

    private HttpStatus getHttpStatus(ServerHttpResponse response) {
        HttpStatus status = response.getStatusCode();
        if (status == null) {
            status = HttpStatus.valueOf(200);
        }
        return status;
    }

    private String getDevice(ServerHttpRequest request) {
        return request.getHeaders().getFirst("User-Agent");
    }

    private String getRemoteAddr(ServerHttpRequest request) {
        return request.getRemoteAddress().getHostString();
    }

    private String getClientId(ServerWebExchange exchange) {
        return UNKNOWN;
    }

    private String getUsername(ServerWebExchange exchange) {
        return UNKNOWN;
    }

    private String getRouteId(ServerWebExchange exchange) {
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        return route == null ? UNKNOWN : route.getId();
    }

    private String getRouteUri(ServerWebExchange exchange) {
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        return route == null ? UNKNOWN : route.getUri().toString();
    }

    private String getResponseCode(ServerWebExchange exchange) {
        MediaType contentType = exchange.getResponse().getHeaders().getContentType();
        if (readable_media_types.contains(contentType)) {
            try {
                JsonNode codeNode = Jackson2Mapper.getObjectMapper().readTree(responseBody).findValue("code");
                if (codeNode != null) {
                    return codeNode.asText("0");
                }
            } catch (Exception e) {
                //.ignore
            }
        }
        if (exchange.getResponse().getStatusCode().value() == 200) {
            return "0";
        }
        return String.valueOf(exchange.getResponse().getStatusCode().value());
    }

    private String getResponseMessage(ServerWebExchange exchange) {
        MediaType contentType = exchange.getResponse().getHeaders().getContentType();
        if (readable_media_types.contains(contentType)) {
            try {
                JsonNode codeNode = Jackson2Mapper.getObjectMapper().readTree(responseBody).findValue("message");
                if (codeNode != null) {
                    return codeNode.asText(StringUtils.EMPTY);
                }
            } catch (Exception e) {
                //.ignore
            }
        }
        if (exchange.getResponse().getStatusCode().value() == 200) {
            return "执行成功";
        }
        return exchange.getResponse().getStatusCode().name();
    }

    private String getRequestContent(ServerHttpRequest request) {
        StringBuffer sb = new StringBuffer("\r\n");
        sb.append("  Headers:\r\n");
        request.getHeaders().entrySet().forEach(entry -> {
            sb.append("    ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
        });
        sb.append("  QueryParams:\r\n");
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        if (queryParams != null) {
            queryParams.toSingleValueMap().entrySet().forEach(entry -> {
                sb.append("    ").append(entry.getKey()).append("=").append(entry.getValue()).append("\r\n");
            });
        }
        sb.append("  Body:\r\n");
        sb.append("    " + this.requestBody);
        return sb.toString();
    }

    private String getResponseContent(ServerHttpResponse response) {
        StringBuffer sb = new StringBuffer("\r\n");
        sb.append("  Headers:\r\n");
        response.getHeaders().entrySet().forEach(entry -> {
            sb.append("    ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
        });
        sb.append("  Body:\r\n");
        sb.append("    " + responseBody);
        return sb.toString();
    }
}
